package com.sr.security.filter;

import com.alibaba.fastjson.JSON;
import com.sr.constant.Constant;
import com.sr.enums.ResponseCode;
import com.sr.exception.Assert;
import com.sr.pojo.SysPermission;
import com.sr.security.JwtAuthenticationToken;
import com.sr.security.SecurityException;
import com.sr.security.UserDetailVo;
import com.sr.util.JwtTokenUtil;
import com.sr.util.RedisUtil;
import io.jsonwebtoken.Claims;
import lombok.Setter;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;

/** 验证过滤器
 * @author xiaochi
 * @date 2022/5/2 19:37
 * @desc JwtTokenAuthenticationFilter
 */
public class JwtTokenAuthenticationFilter extends OncePerRequestFilter {

    @Setter
    private String[] excludes;
    @Setter
    private LoginUserFilter userFilter;

    private static AntPathMatcher antPathMatcher = new AntPathMatcher();

    /**
     * 验权（每次访问都会进入验证）
     * @param request
     * @param response
     * @param filterChain
     * @throws ServletException
     * @throws IOException
     */
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        String uri = request.getServletPath();
        try {
            if (antPathMatcher.match(Constant.Api.BACK_PREFIX+"/**",uri) && Arrays.stream(excludes).noneMatch(x -> antPathMatcher.match(x,uri))){
                this.checkToken(request);
            }
        }catch (AuthenticationException e){
            logger.error("权限验证异常:{}",e);
            // 交给自定义 AuthentFailureHandler 错误处理器
            userFilter.unsuccessfulAuthentication(request,response,e);
            return;
        }
        filterChain.doFilter(request,response);
    }

    /**
     * 验证token
     * @param request
     */
    private void checkToken(HttpServletRequest request) {
        String token = request.getHeader(Constant.User.ACCESS_TOKEN);
        Assert.assertNotNull(token,new SecurityException(ResponseCode.TOKEN_NOT_NULL));
        Assert.isTure(!JwtTokenUtil.isTokenExpired(token),new SecurityException(ResponseCode.TOKEN_NO_AVAIL));
        String username = this.username(token);
        Assert.assertNotNull(username,new SecurityException(ResponseCode.TOKEN_NO_AVAIL));
        Assert.isTure(RedisUtil.hasKey(Constant.User.BACK_USER_KEY+username) && RedisUtil.hasKey(Constant.User.BACK_USER_TOKEN_KEY+username), new SecurityException(ResponseCode.SYSTEM_USERNAME_OFFLINE.getMessage()));
        String userCacheToken = RedisUtil.get(Constant.User.BACK_USER_TOKEN_KEY + username);
        Assert.assertNotNull(userCacheToken,new SecurityException(ResponseCode.SYSTEM_USERNAME_OFFLINE.getMessage()));
        Assert.isTure(Objects.equals(token,userCacheToken),new SecurityException(ResponseCode.TOKEN_EXISTS.getMessage()));
        String userDetailVo = RedisUtil.get(Constant.User.BACK_USER_KEY + username);
        Assert.assertNotNull(userDetailVo,new SecurityException(ResponseCode.TOKEN_NO_AVAIL.getMessage()));
        UserDetailVo userDetailsVo = null;
        try {
            userDetailsVo = JSON.parseObject(userDetailVo, UserDetailVo.class);
            List<SysPermission> permissions = JSON.parseArray(RedisUtil.get(Constant.User.BACK_USER_PERMISSION_KEY + username), SysPermission.class);
            Assert.assertNotNull(permissions,new SecurityException(ResponseCode.NOT_PERMISSION.getMessage()));
            String[] permsList = permissions.stream().filter(x -> !StringUtils.isEmpty(x.getPerms())).map(SysPermission::getPerms).distinct().toArray(String[]::new);
            userDetailsVo.setAuthorities(AuthorityUtils.createAuthorityList(permsList));
        }catch (Exception e){
            logger.error("缓存获取用户信息失败，{}",e);
        }finally {
            Assert.assertNotNull(userDetailsVo,new SecurityException(ResponseCode.TOKEN_ERROR.getMessage()));
        }
        // 把验证通过的 token 交给 security
        JwtAuthenticationToken authenticationToken = new JwtAuthenticationToken(userDetailsVo.getUsername(), userDetailsVo.getPassword(), userDetailsVo.getAuthorities());
        authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
        // 设置为已登录
        SecurityContextHolder.getContext().setAuthentication(authenticationToken);
    }

    /**
     * 从 token 获取 username
     * @param token
     * @return
     */
    private String username(String token){
        String username = "";
        try {
            Claims claims = JwtTokenUtil.parseToken(token);
            if (claims != null){
                username = (String) claims.get(Constant.User.JWT_USERNAME);
            }
        }catch (Exception ignored){}
        return username;
    }
}
